package com.orchidea.uml.view.presentation
{
import com.orchidea.uml.events.DocumentElementEvent;
import com.orchidea.uml.events.DocumentEvent;
import com.orchidea.uml.events.UndoEvent;
import com.orchidea.uml.manager.command.IUndoable;
import com.orchidea.uml.model.UmlEntityType;
import com.orchidea.uml.model.document.Document;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import mx.binding.utils.BindingUtils;
import mx.collections.ICollectionView;

public class DocumentPresentation extends EventDispatcher
    {

        /** This property is injected by the application. */
        public var dispatcher : IEventDispatcher;

        private var _undoPossible  : Boolean;
        /** This property is injected by the application. */
        public function set undoPossible( value : Boolean ) : void
        {
            _undoPossible = value;
            dispatchEvent(new Event("undoStateChanged"));
        }

        private var _redoPossible  : Boolean;
        /** This property is injected by the application. */
        public function set redoPossible( value : Boolean ) : void
        {
            _redoPossible = value;
            dispatchEvent(new Event("undoStateChanged"));
        }

        private var _documentDirty : Boolean;
        /** This property is injected by the application. */
        public function set documentDirty( value : Boolean ) : void
        {
            _documentDirty = value;
            dispatchEvent(new Event("documentDirtyChanged"));
        }

        private var _title : String;
        [Bindable(event="titleChanged")]
		public function get title() : String
        {
			return _title || "";
		}

		public function set title(value:String):void
        {
			_title = value;
			update();
		}

        private var _undoHistory : Array;
        /**
         * Returns the undo history sorted from the most recent command to the oldest.
         */
        [Bindable]
        public function get undoHistory( ) : Array
        {
            // this may look weird, but does a few necessary things that
            // aren't easy to express in ActionScript:
            // - it avoids the possibility that the undoHistory is null
            // - it creates a copy of the array to avoid the possibility that
            //   the caller changes the list outside of this object's control
            return (_undoHistory || []).slice().reverse();
        }

        /** The undo history is injected by the application. */
        public function set undoHistory( value : Array ) : void
        {
            _undoHistory = value;
        }

        private var _document : Document;
        /**
		 * This property is injected by the application. The getter is only here because
		 * BindingUtils.bindSetter throws an error for read-only properties (see the constructor).
		 */
		[Bindable]
		public function get document( ) : Document
        {
			return _document;
		}

		public function set document(value:Document):void
        {
			_document = value;
			// as a side effect of this property being set the titleChanged
			// and textChanged methods will be called, because of the bindings
			// set up in the constructor
		}

        /** This property is injected by the application. */ 
        [Bindable(event="elementsChanged")]
        public var elements:ICollectionView;

        /*
		 * The following three getters encapsulate the logic that determines
		 * if the undo, redo and save buttons should be enabled. The view
		 * only needs to bind to these three, which keeps it simple.
		 */

		[Bindable(event="undoStateChanged")]
		public function get undoButtonEnabled():Boolean
        {
			return _undoPossible;
		}

		[Bindable(event="undoStateChanged")]
		public function get redoButtonEnabled():Boolean
        {
			return _redoPossible;
		}

		[Bindable(event="documentDirtyChanged")]
		public function get saveButtonEnabled():Boolean
        {
			return _documentDirty;
		}


        public function DocumentPresentation()
        {
			// whenever the document property or the title and text properties of the
			// document changes, call the titleChanged and/or textChanged methods
			BindingUtils.bindSetter(titleChanged, this, ["document", "title"]);
            BindingUtils.bindSetter(elementsChanged, this, ["document", "elements"]);
		}

        private function titleChanged( title : String ) : void
        {
			_title = title;
			dispatchEvent(new Event("titleChanged"));
		}

        private function elementsChanged(elements:ICollectionView):void
        {
            dispatchEvent(new Event("elementsChanged"));
        }
        
        [Bindable(event="titleChanged")]
		[Bindable(event="documentDirtyChanged")]
		public function get displayTitle():String
        {
			return title + (_documentDirty ? " *" : "");
		}

        /**
		 * Returns a prettified label for an undoable command, the label contains the
		 * index of the command in the history (1 is most recent), the description and
		 * the timestamp in HH:MM:SS format.
		 */
		public function labelForUndoable(undoable:IUndoable):String
        {
			var hours   : String = (undoable.timestamp.hours   < 10 ? "0" : "") + undoable.timestamp.hours;
			var minutes : String = (undoable.timestamp.minutes < 10 ? "0" : "") + undoable.timestamp.minutes;
			var seconds : String = (undoable.timestamp.seconds < 10 ? "0" : "") + undoable.timestamp.seconds;
			var index   : int    = undoHistory.indexOf(undoable) + 1;

			return index + ". " + undoable.description + " (" + hours + ":" + minutes + ":" + seconds + ")";
		}

        /**
		 * Notifies the application that the document needs updating.
		 */
		private function update() : void {
			var event : DocumentEvent = new DocumentEvent(DocumentEvent.UPDATE);

			event.reference = document;
            event.documentTitle = _title;
//			event.data      = new DocumentData(_title, _text);
			
			dispatcher.dispatchEvent(event);
		}

        /**
         * Notifies the application that the item need to be added into document
         * @param itemType Type of item to add
         */
        public function addItem(itemType:UmlEntityType):void
        {
            var event:DocumentElementEvent = new DocumentElementEvent(DocumentElementEvent.CREATE);
            event.elementType = itemType;

            dispatcher.dispatchEvent(event);
        }

		/**
		 * Notifies the application that the document needs saving (after first calling update).
		 */
		public function save():void
        {
			update();

			var event : DocumentEvent = new DocumentEvent(DocumentEvent.SAVE);

			event.reference = document;

			dispatcher.dispatchEvent(event);
		}

        /**
		 * Notifies the application that the user has requested an undo. When the
		 * application handles the undo request the document may change, and this
		 * will be handled as any other change in the document's state.
		 */
		public function undo():void
        {
			dispatcher.dispatchEvent(new UndoEvent(UndoEvent.UNDO));
		}

		/**
		 * Notifies the application that the user has requested a redo.
		 */
		public function redo():void
        {
			dispatcher.dispatchEvent(new UndoEvent(UndoEvent.REDO));
		}

		/**
		 * Notifies the application that the user has requested a restore.
		 */
		public function restore(undoable:IUndoable):void
        {
			dispatcher.dispatchEvent(new UndoEvent(UndoEvent.RESTORE, undoable));
		}
    }
}